/**
 * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright ownership. Apereo
 * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at the
 * following location:
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apereo.portal.version;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apereo.portal.version.om.Version;
import org.apereo.portal.version.om.Version.Field;

/** Utilities for working with Version classes */
public class VersionUtils {
    private static final Pattern VERSION_PATTERN =
            Pattern.compile("^(\\d+)\\.(\\d+)\\.(\\d+)(?:\\.(\\d+))?(?:[\\.-].*)?$");

    /**
     * Parse a version string into a Version object, if the string doesn't match the pattern null is
     * returned.
     *
     * <p>The regular expression used in parsing is: ^(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?(?:[\.-].*)?$
     *
     * <p>Examples that match correctly:
     *
     * <ul>
     *   <li>4.0.5
     *   <li>4.0.5.123123
     *   <li>4.0.5-SNAPSHOT
     *       <ul>
     *         Examples do NOT match correctly:
     *         <ul>
     *           <li>4.0
     *           <li>4.0.5_123123
     *               <ul>
     */
    public static Version parseVersion(String versionString) {
        final Matcher versionMatcher = VERSION_PATTERN.matcher(versionString);
        if (!versionMatcher.matches()) {
            return null;
        }

        final int major = Integer.parseInt(versionMatcher.group(1));
        final int minor = Integer.parseInt(versionMatcher.group(2));
        final int patch = Integer.parseInt(versionMatcher.group(3));
        final String local = versionMatcher.group(4);
        if (local != null) {
            return new SimpleVersion(major, minor, patch, Integer.valueOf(local));
        }

        return new SimpleVersion(major, minor, patch);
    }

    /**
     * Determine how much of two versions match. Returns null if the versions do not match at all.
     *
     * @return null for no match or the name of the most specific field that matches.
     */
    public static Version.Field getMostSpecificMatchingField(Version v1, Version v2) {
        if (v1.getMajor() != v2.getMajor()) {
            return null;
        }

        if (v1.getMinor() != v2.getMinor()) {
            return Version.Field.MAJOR;
        }

        if (v1.getPatch() != v2.getPatch()) {
            return Version.Field.MINOR;
        }

        final Integer l1 = v1.getLocal();
        final Integer l2 = v2.getLocal();
        if (l1 != l2 && (l1 == null || l2 == null || !l1.equals(l2))) {
            return Version.Field.PATCH;
        }

        return Version.Field.LOCAL;
    }

    /**
     * Determine if an "update" can be done between the from and to versions. The ability to update
     * is defined as from == to OR (from.isBefore(to) AND mostSpecificMatchingField in (PATCH,
     * MINOR))
     *
     * @param from Version updating from
     * @param to Version updating to
     * @return true if the major and minor versions match and the from.patch value is less than or
     *     equal to the to.patch value
     */
    public static boolean canUpdate(Version from, Version to) {
        final Field mostSpecificMatchingField = getMostSpecificMatchingField(from, to);
        switch (mostSpecificMatchingField) {
            case LOCAL:
                {
                    return true;
                }
            case PATCH:
            case MINOR:
                {
                    return from.isBefore(to);
                }
            default:
                {
                    return false;
                }
        }
    }

    private static final class SimpleVersion extends AbstractVersion {
        private static final long serialVersionUID = 1L;

        private final int major;
        private final int minor;
        private final int patch;
        private final Integer local;

        public SimpleVersion(int major, int minor, int patch) {
            this.major = major;
            this.minor = minor;
            this.patch = patch;
            this.local = null;
        }

        public SimpleVersion(int major, int minor, int patch, Integer local) {
            this.major = major;
            this.minor = minor;
            this.patch = patch;
            this.local = local;
        }

        @Override
        public int getMajor() {
            return major;
        }

        @Override
        public int getMinor() {
            return minor;
        }

        @Override
        public int getPatch() {
            return patch;
        }

        @Override
        public Integer getLocal() {
            return local;
        }
    }
}
